Форматированный вывод
=====================

.. index::
    std.format

Эта глава посвящена возможностям, которые описаны в модуле std.format, то есть вне ядра языка D.
Подобно всем модулям имеющим префикс std, std.format - модуль
включенный в Фобос (Phobos), то есть в стандартную библиотеку языка D. Полное описание этой библиотеки
лежит за пределами этой книги.

Спецификаторы формата языка D очень похожи на аналогичные спецификаторы языка С. Перед тем как двигаться дальше
Я хотел привести общую информацию по спецификаторам формата для дальнейших ссылок

.. index::
    спецификаторы формата
    строка формата

.. code-block:: console

    Флаги (могут объединяться)
    -    сдвиг влево
    +    печать знака
    #    печать альтернативным способом
    0    заполнение нулями

    Спецификаторы формата
    s  по умолчанию
    b  двоичный
    d  десятичный
    o  восьмеричный
    x,X  шестнадцатиричный
    f,F - плавающая точка в десятичной форме
    e,E - плавающая точка в научной нотации
    a,A - плавающая точка в шестнадцатиричной нотации
    g,G - синоним e или f
    ,     разделитель цифр
    (    начало елемента формата
    )    завершение элемента формата
    |   разделитель элементов

Мы использовали такие функции, как writeln, с несколькими параметрами по мере
необходимости для печати желаемого результата. Параметры будут преобразованы в их
строковые представления, а затем отправлены на вывод.
Иногда этого недостаточно. Выходные данные могут быть в очень специфическом
формате. Давайте рассмотрим следующий код, который используется для печати позиций счета-фактуры:


.. code-block:: d

    items ~= 1.23;
    items ~= 45.6;
    for (int i = 0; i != items.length; ++i)
    {
        writeln("Item ", i + 1, ": ", items[i]);
    }

Вывод

.. code-block:: console

    Item 1: 1.23
    Item 2: 45.6

Несмотря на то, что информация верна, нам может потребоваться напечатать ее в
другом формате. Например, возможно, десятичные знаки (в данном случае точки)
должны выстраиваться в линию, и мы должны убедиться, что после десятичного знака всегда будут две цифры
, как в следующем выводе:

.. code-block:: d

    Item 1:     1.23
    Item 2:    45.60

В таких случаях полезен форматированный вывод. Функции вывода, которые мы
использовали до сих пор, имеют аналоги, содержащие букву f в названиях: writef()
и writefln() . Буква f является сокращением от formatted. Первым параметром этих
функций является строка формата, которая описывает, как должны быть
напечатаны остальные параметры.
Например, функция writefln() может выдать желаемый результат, указанный выше, со
следующей строкой формата:

.. code-block:: d

    writefln("Item %d:%9.02f", i + 1, items[i]);

Строка формата содержит обычные символы, которые передаются в выходные данные как есть,
а также специальные спецификаторы формата, соответствующие каждому параметру, который должен быть
напечатан. Спецификаторы формата начинаются с символа % и заканчиваются символом формата.
Приведенная выше строка формата содержит два спецификатора формата: %d и %9.02f .
Каждый спецификатор связан с соответствующим параметром, обычно в порядке
появления. Например, %d связан с i + 1, а %9.02f связан
с элементами[i] . Каждый спецификатор определяет формат параметра, который он использует.
соответствует. (Спецификаторы формата также могут иметь номера параметров. Это
будет объяснено позже в этой главе.)
Все остальные символы строки формата, которые не являются частью
спецификаторов формата, печатаются как есть. Такие обычные символы приведенного выше спецификатора формата
выделены в "Item %d:%9.02f" .
Спецификаторы формата состоят из нескольких частей, большинство из которых необязательны. Часть
с именем position будет объяснена ниже. Остальные следующие: (Примечание:
Пробелы между этими частями вставлены здесь для удобства чтения; они не являются частью спецификаторов.)

.. code-block:: d

    % flags width separator precision format_character

Символ % в начале и символ формата в конце обязательны; остальные необязательны.
Поскольку % имеет особое значение в строках формата, когда нам нужно напечатать % как
обычный символ, мы должны ввести его как %% .

Символы форматирования
----------------------

.. index::
    символы форматирования

* b: Целочисленный аргумент выводится в двоичной системе.
* o: Целочисленный аргумент выводится в восьмеричной системе.
* x и X : Целочисленный аргумент выводится в шестнадцатеричной системе счисления;
  строчными буквами при использовании x и прописными буквами при использовании X .
* d: Целочисленный аргумент выводится в десятичной системе счисления; отрицательный знак также
  выводится, если это тип со знаком и значение меньше нуля.

.. code-block:: d

    int value = 12;
    writefln("Binary : %b", value);
    writefln("Octal : %o", value);
    writefln("Hexadecimal: %x", value);
    writefln("Decimal: %d", value);

Вывод

.. code-block:: console

    Binary :  1100
    Octal :   14
    Hexadecimal: c
    Decimal: 12

* e : Аргумент с плавающей точкой печатается в соответсвии со следующими правилами

#. одна цифра перед десятичным знаком
#. десятичный знак, если точность отлична от нуля
#. требуемые цифры после десятичного знака, количество которых
   определяется точностью (точность по умолчанию равна 6)
#. символ e (означает "10 в степени")
#. символ - или +, в зависимости от того, меньше или
   больше нуля показатель степени
#. показатель степени, состоящий как минимум из двух цифр

* E: То же, что и e, за исключением вывода символа E вместо e .
* f и F : Аргумент с плавающей запятой выводится в десятичной системе счисления;
  перед десятичным знаком стоит по крайней мере одна цифра, а точность по умолчанию составляет 6 цифр после
  десятичного знака
* g: То же, что и f, если показатель степени находится между 5 и точностью; в противном случае то же, что и e .
  точность определяет не количество цифр после десятичной запятой, а
  значащие цифры всего значения. Если после
  десятичного знака нет значащих цифр, то десятичный знак не печатается. Крайние правые нули после
  десятичного знака не печатаются.
* G : То же , что и g , за исключением вывода символа E .
* a: Аргумент с плавающей запятой печатается в шестнадцатеричной
  системе счисления с плавающей запятой

#. символы 0x
#. одна шестнадцатеричная цифра
   десятичный знак, если точность отлична от нуля,
   требуемые цифры после десятичного знака, количество которых
   определяется точностью; если точность не указана, то столько цифр, сколько необходимо
#. символ p (означает "2 в степени")
#. символ - или +, в зависимости от того, меньше или
   больше нуля показатель степени
#. показатель степени, состоящий по крайней мере из одной цифры (показатель степени значения 0 равен 0)

* A : То же , что и a , за исключением вывода символов 0X и P .

.. code-block:: d

    double value = 123.456789;
    writefln("with e: %e", value);
    writefln("with f: %f", value);
    writefln("with g: %g", value);
    writefln("with a: %a", value);

.. code-block:: console

    with e: 1.234568e+02
    with f: 123.456789
    with g: 123.457
    with a: 0x1.edd3c07ee0b0bp+6

* s: Значение выводится таким же образом, как и при обычном выводе, в соответствии с типом
  аргумента:

#. значения bool как true или false
#. целочисленные значения, такие же, как %d
#. значения с плавающей запятой, такие же, как %g
#. строки в кодировке UTF-8; точность определяет максимальное количество
   используемых байт (помните, что в кодировке UTF-8 количество байт не является
   совпадает с количеством символов; например, строка "ağ" содержит 2
   символа, состоящих в общей сложности из 3 байт)
#. объекты struct и class в качестве возвращаемого значения функций-членов toString()
   их типов; точность определяет максимальное количество байт
   для использования
#. массивы последовательность значений их элементов

.. code-block:: d

    bool b = true;
    int i = 365;
    double d = 9.87;
    string s = "formatted";
    auto o = File("test_file", "r");
    int[] a = [ 2, 4, 6, 8 ];
    writefln("bool : %s", b);
    writefln("int : %s", i);
    writefln("double: %s", d);
    writefln("string: %s", s);
    writefln("object: %s", o);
    writefln("array : %s", a);

.. code-block:: console

    bool : true
    int : 365
    double: 9.87
    string: formatted
    object: File(55738FA0)
    array : [2, 4, 6, 8]

Ширина поля вывода
------------------

.. index::
    ширина поля выводв

Эта часть определяет ширину поля, в котором выводится аргумент. Если
ширина указана в виде символа \*, то фактическое значение ширины считывается из
следующего аргумента (этот аргумент должен быть int). Если ширина является отрицательным значением, то
предполагается флаг -.

.. code-block:: d

    int value = 100;
    writefln("In a field of 10 characters:%10s", value);
    writefln("In a field of 5 characters :%5s", value);

.. code-block:: console

    In a field of 10 characters:         100
    In a field of 5 characters : 100

Разделитель
-----------

Символ запятой указывает на использование разделителя между группами чисел.
Количество цифр в группе по умолчанию равно 3, но оно может быть указано после запятой:

.. code-block:: d

    writefln("%,f", 1234.5678); // группы по 3 числа
    writefln("%,s", 1000000);   // группы по 3 числа
    writefln("%,2s", 1000000);  // группы по 2 числа

Вывод

.. code-block:: console

    1,234.567,800
    1,000,000
    1,00,00,00

Если количество цифр указано в виде символа \*, то фактическое количество
цифр считывается из следующего аргумента (этот аргумент должен быть int).

.. code-block:: d

    writefln("%,*s", 1, 1000000); // группа из одного числа


Вывод

..  code-block:: console

        1,0,0,0,0,0,0

Аналогично, можно указать символ-разделитель, используя вопросительный
знак после запятой и указав символ в качестве дополнительного аргумента
перед числом:

.. code-block:: d

    writefln("%,?s", '.', 1000000); // The разделитель '.'

.. code-block:: console

    1.000.000

Точность
--------

Точность указывается после точки в спецификаторе формата. Для типов с плавающей запятой она
определяет точность печатного представления значений. Если точность указана в виде символа \*,
то фактическая точность считывается из следующего аргумента (этот аргумент должен быть int).
Отрицательные значения точности игнорируются.


.. code-block:: d

    double value = 1234.56789;
    writefln("%.8g", value);
    writefln("%.3g", value);
    writefln("%.8f", value);
    writefln("%.3f", value);

.. code-block:: console

    1234.5679
    1.23e+03
    1234.56789000
    1234.568

.. code-block:: d

    auto number = 0.123456789;
    writefln("Number: %.*g", 4, number);

.. code-block:: console

    Number: 0.1235

Флаги
------

.. index::
    флаги

Можно указать более одного флага.
-: значение выводится выровненным по левому краю в своем поле; этот флаг отменяет флаг 0

.. code-block:: d

    int value = 123;
    writefln("Normally right-aligned:|%10d|", value);
    writefln("Left-aligned :|%-10d|", value);

.. code-block:: console

    Normally right-aligned:|   123|
    Left-aligned :|123    |

+: если значение положительное, перед ним ставится символ +; этот флаг отменяет флаг
пробела

.. code-block:: d

    writefln("No effect for negative values : %+d", -50);
    writefln("Positive value with the + flag : %+d", 50);
    writefln("Positive value without the + flag: %d", 50);

.. code-block:: console

    No effect for negative values : -50
    Positive value with the + flag : +50
    Positive value without the + flag: 50

#: выводит значение в альтернативной форме в зависимости от format_character

* o: первый символ восьмеричного значения всегда выводится как 0
* x и X: если значение не равно нулю, перед ним добавляется 0x или 0XX
  с плавающей запятой: десятичная метка печатается, даже если
  после десятичной метки нет значащих цифр
* g и G: печатаются даже незначащие нулевые цифры после десятичной метки

.. code-block:: d

    writefln("Octal starts with 0 : %#o", 1000);
    writefln("Hexadecimal starts with 0x : %#x", 1000);
    writefln("Contains decimal mark even when unnecessary: %#g", 1f);
    writefln("Rightmost zeros are printed : %#g", 1.2);

.. code-block:: console

    Octal starts with 0 : 01750
    Hexadecimal starts with 0x : 0x3e8
    Contains decimal mark even when unnecessary: 1.00000
    Rightmost zeros are printed : 1.20000

0: поле заполняется нулями (если значение не равно nan или бесконечности); если
также указана точность, этот флаг игнорируется

.. code-block:: d

    writefln("In a field of 8 characters: %08d", 42);

.. code-block:: console

    In a field of 8 characters: 00000042

символ пробела: если значение положительное, перед ним добавляется символ пробела для выравнивания
отрицательного и положительного значений

.. code-block:: d

    writefln("No effect for negative values: % d", -34);
    writefln("Positive value with space : % d", 56);
    writefln("Positive value without space : %d", 56);

.. code-block:: console

    No effect for negative values: -34
    Positive value with space :     56
    Positive value without space : 56

Позиционные параметры
---------------------

Выше мы видели, что аргументы связаны один за другим со
спецификаторами в строке формата. Также возможно использовать номера позиций в
спецификаторах формата. Это позволяет связать спецификаторы с конкретными аргументами.
Аргументы нумеруются по возрастанию, начиная с 1. Номера аргументов
указываются сразу после символа %, за которым следует символ $:

.. code-block:: d

    % position$ flags width precision format_character

Преимуществом позиционных параметров является возможность использовать один и тот же аргумент
более чем в одном месте в одной и той же строке формата

.. code-block:: d

    writefln("%1$d %1$x %1$o %1$b", 42);

Приведенная выше строка формата использует аргумент с номером 1 в четырех спецификаторах для
печати в десятичном, шестнадцатеричном, восьмеричном и двоичном форматах:

.. code-block:: console

    42 2a 52 101010

Другим применением позиционных параметров является поддержка нескольких естественных
языков. При указании номеров позиций аргументы могут быть перемещены
в любое место в строке определенного формата для данного человеческого языка.
Например, количество учащихся в данном классе может быть напечатано следующим образом


.. code-block:: d

    writefln("There are %s students in room %s.", count, room);

.. code-block:: console

    There are 20 students in room 1A.

Давайте предположим, что программа также должна поддерживать турецкий. В этом случае строка формата
должна быть выбрана в соответствии с активным языком. Следующий
метод использует преимущества тернарного оператора:

.. code-block:: d

    auto format = (language == "en" ? "There are %s students in room %s." : "%s sınıfında %s öğrenci var.");
    writefln(format, count, room);

К сожалению, когда аргументы связаны один за другим, информация о классе и
количестве учащихся отображается в сообщении на турецком языке в обратном порядке;
информация о комнате находится там, где должен быть счетчик, а количество - там, где
должна быть комната:

.. code-block:: console

    20 sınıfında 1A öğrenci var. ← Wrong: means "room 20", and "1A students"!

20 sınıfında 1A öğrenci var. ← Неправильно: означает "комната 20" и "студенты 1А"!

Чтобы избежать этого, аргументы могут быть указаны числами, такими как 1$ и 2$, чтобы
связать каждый указанный аргумент с точным:

.. code-block:: d

    auto format = (language == "en" ? "There are %1$s students in room %2$s." : "%2$s sınıfında %1$s öğrenci var.");
    writefln(format, count, room);

Теперь аргументы отображаются в правильном порядке, независимо от
выбранного языка

.. code-block:: console

    There are 20 students in room 1A.

.. code-block:: console

    1A sınıfında 20 öğrenci var.

Форматирование элементов контейнеров
------------------------------------

Спецификаторы формата между %( и %) применяются к каждому элементу контейнера
(например, массиву или диапазону).:

.. code-block:: d

    auto numbers = [ 1, 2, 3, 4 ];
    writefln("%(%s%)", numbers);

Приведенная выше строка формата состоит из трех частей:
* %(: Начало формата элемента
* %s: Формат для каждого элемента
* %): Конец формата элемента
При печати каждого элемента в формате %s элементы отображаются один за другим:

.. code-block:: console

    1234

Обычные символы до и после формата элемента повторяются для каждого
элемента. Например, спецификатор {%s}, будет выводить каждый элемент в
фигурных скобках, разделенных запятыми:

.. code-block:: d

    writefln("%({%s},%)", numbers);

Однако обычные символы справа от спецификатора формата считаются
разделителями элементов и печатаются только между элементами, а не после последнего


.. code-block:: console

    {1},{2},{3},{4 ← '}'  ',' не напечатано после последнего символа

%| используется для указания символов, которые должны быть напечатаны даже для последнего
элемента. Символы, расположенные справа от %|, считаются разделителями
и не печатаются для последнего элемента. И наоборот, символы слева от %|
печатаются даже для последнего элемента.
Например, следующий спецификатор формата выводит закрывающую фигурную
скобку после последнего элемента, но не запятую:

.. code-block:: d

    writefln("%({%s}%|,%)", numbers);

.. code-block:: console

    {1},{2},{3},{4} ← '}' напечатано после последнего символа


В отличие от строк, которые печатаются по отдельности, строки, которые печатаются как элементы
, заключаются в двойные кавычки:

.. code-block:: d

    auto vegetables = [ "spinach", "asparagus", "artichoke" ];
    writefln("%(%s, %)", vegetables);

.. code-block:: console

    "spinach", "asparagus", "artichoke"

Если двойные кавычки нежелательны, формат элемента должен начинаться с
%-( вместо %(:

.. code-block:: d

    writefln("%-(%s, %)", vegetables);

.. code-block:: console

    spinach, asparagus, artichoke

То же самое относится и к символам. %( выводит их в одинарных кавычках:

.. code-block:: d

    writefln("%(%s%)", "hello");

.. code-block:: console

    'h''e''l''l''o'

%-( выводит их без кавычек:

.. code-block:: d

    writefln("%-(%s%)", "hello");

.. code-block:: console

    hello

Для ассоциативных массивов должно быть два спецификатора формата: один для ключей и
один для значений. Например, следующий спецификатор %s (%s) сначала напечатает
ключ, а затем значение в круглых скобках:

.. code-block:: d

    auto spelled = [ 1 : "one", 10 : "ten", 100 : "hundred" ];
    writefln("%-(%s (%s)%|, %)", spelled);

Также обратите внимание, что, будучи указанной справа от %|, запятая не выводится для
последнего элемента:

.. code-block:: console

    1 (one), 100 (hundred), 10 (ten)

Функция format
--------------

Форматированный вывод также доступен через функцию format() модуля std.string
. функция format() работает так же, как и функция write(), но возвращает результат в виде
строки вместо того, чтобы отправить его на печать:

.. code-block:: d

    import std.stdio;
    import std.string;

    void main()
    {
        write("What is your name? ");
        auto name = strip(readln());
        auto result = format("Hello %s!", name);
    }


Программа может использовать этот результат в последующих выражениях.

Проверка строки формата
-----------------------

В стандартной библиотеке существует альтернативный синтаксис для таких функций, как format,
которые принимают строку формата (writef, writefln, formatted Write, readf,
formattedRead и т.д.). Можно предоставить строку формата в качестве
аргумента шаблона для этих функций, чтобы во время компиляции проверялась достоверность строки формата и
аргументов:


.. code-block:: d

    import std.stdio;

    void main()
    {
        writefln!"%s %s"(1); // ← ошибка компиляции (излишний %s)
        writefln!"%s"(1, 2); // ← ошибка компиляции (лишний параметр 2)
        writefln!"%s %d"(1, 2.5); // ← ошибка компиляции (не совпадения %d and 2.5)
    }

Символ ! выше - это оператор создания экземпляра шаблона, который мы рассмотрим в
следующей главе (стр. 399).
*(Примечание: Хотя этот синтаксис более безопасен, поскольку он улавливает потенциальные ошибки программиста
во время компиляции, он также может увеличить время компиляции.)*

Упражнения

#. Напишите программу, которая считывает значение и выводит его в шестнадцатеричной системе.
#. Напишите программу, которая считывает значение с плавающей запятой и выводит его в виде процентного
   значения с двумя цифрами после десятичной запятой. Например, если значение равно
   1.2345, должно быть выведено% 1.23.

Решения :ref:`Форматированный вывод<Форматированный вывод>`
